package de.fhffm.scs.simulator.scheduler;

import static java.util.concurrent.TimeUnit.MILLISECONDS;

import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SimulationScheduler extends Thread implements Scheduler {

    static private Logger LOGGER = LoggerFactory.getLogger(SimulationScheduler.class);

    static private final long SLEEP_BETWEEN_UPDATES_MS = 100;

    static private final int REAL_WORLD_SIMULATION_SPEED = 1;

    private int simulationSpeed = REAL_WORLD_SIMULATION_SPEED;

    private static Collection<TimeUnit> SUPPORTED_TIME_UNITS;

    static {
        SUPPORTED_TIME_UNITS = new ArrayList<TimeUnit>();
        SUPPORTED_TIME_UNITS.add(TimeUnit.SECONDS);
        SUPPORTED_TIME_UNITS.add(TimeUnit.MINUTES);
    }

    private long simulatedTimestamp;

    public SimulationScheduler(long startTimestamp) {
        this.simulatedTimestamp = startTimestamp;
    }

    @Override
    public long getCurrentTimestamp() {
        return simulatedTimestamp;
    }

    @Override
    public void sleep(TimeUnit unit, long value) throws InterruptedException, IllegalArgumentException {
        if (isSupported(unit)) {
            long realSleepDuration = unit.toMillis(value) / simulationSpeed;
            LOGGER.trace("Real sleep duration is about {} seconds", MILLISECONDS.toSeconds(realSleepDuration));
            Thread.sleep(realSleepDuration);
        } else {
            throw new IllegalArgumentException("TimeUnit " + unit + " supported.");
        }
    }

    public boolean isSupported(TimeUnit unit) {
        return SUPPORTED_TIME_UNITS.contains(unit);
    }

    public int getSimulationSpeed() {
        return simulationSpeed;
    }

    public void setSimulationSpeed(int simulationSpeed) {
        this.simulationSpeed = simulationSpeed;
    }

    @Override
    public void run() {
        setName(simpleName());
        while (!isInterrupted()) {
            try {
                Thread.sleep(SLEEP_BETWEEN_UPDATES_MS);
                updateSimulationTime();
            } catch (InterruptedException e) {
                interrupt();
            }
        }
    }

    protected void updateSimulationTime() {
        simulatedTimestamp += SLEEP_BETWEEN_UPDATES_MS * simulationSpeed;
    }

    private String simpleName() {
        return getClass().getSimpleName();
    }

    @Override
    public String toString() {
        return simpleName()
            + " (Speed: "
            + simulationSpeed
            + "x)";
    }

}
